<?php if(! defined('SYSPATH')) exit('No direct script access allowed');
/*
 +----------------------------------------------------------------------+
 | QuickPHP Framework Version 0.10                                      |
 +----------------------------------------------------------------------+
 | Copyright (c) 2010 QuickPHP.net All rights reserved.                 |
 +----------------------------------------------------------------------+
 | Licensed under the Apache License, Version 2.0 (the 'License');      |
 | you may not use this file except in compliance with the License.     |
 | You may obtain a copy of the License at                              |
 | http://www.apache.org/licenses/LICENSE-2.0                           |
 | Unless required by applicable law or agreed to in writing, software  |
 | distributed under the License is distributed on an 'AS IS' BASIS,    |
 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
 | implied. See the License for the specific language governing         |
 | permissions and limitations under the License.                       |
 +----------------------------------------------------------------------+
 | Author: BoPo <ibopo@126.com>                                         |
 +----------------------------------------------------------------------+
*/
/**
 * QuickPHP 核心类，应用程序的调度工作
 *
 * @category   QuickPHP
 * @package    QuickPHP
 * @copyright  Copyright (c) 2010 http://quickphp.net All rights reserved.
 * @license    http://framework.quickphp.net/licenses/LICENSE-2.0
 * @version    $Id: QuickPHP.php 7245 2011-01-23 05:19:07Z bopo $
 */
class QuickPHP
{
    const VERSION = '1.0.2';
    const CODENAME = 'hattrick';

    /**
     * 日志消息类型
     */
    const ERROR = 'ERROR';
    const DEBUG = 'DEBUG';
    const INFO  = 'INFO';

    /**
     * PHP文件开头安全检查代码
     *
     */
    const FILE_SECURITY = '<?php defined(\'SYSPATH\') or die(\'No direct script access.\');';

    /**
     * @var  array  PHP 错误码
     */
    public static $php_errors = array(
        E_ERROR             => 'Fatal Error',
        E_USER_ERROR        => 'User Error',
        E_PARSE             => 'Parse Error',
        E_WARNING           => 'Warning',
        E_USER_WARNING      => 'User Warning',
        E_STRICT            => 'Strict',
        E_NOTICE            => 'Notice',
        E_RECOVERABLE_ERROR => 'Recoverable Error'
    );

    /**
     * @var  boolean  判断是否命令行环境
     */
    public static $is_cli = FALSE;

    /**
     * @var  boolean  判断是否是windows环境
     */
    public static $is_windows = FALSE;

    /**
     * @var  boolean  判断是否开启魔术引号
     */
    public static $magic_quotes = FALSE;

    /**
     * @var  boolean  日志记录错误以及异常信息
     */
    public static $log_errors = FALSE;

    /**
     * @var  string  输入和输出的编码
     */
    public static $charset = 'UTF-8';

    /**
     * @var  string  网站域名
     */
    public static $domain = '/';

    /**
     * @var  string  默认协议
     */
    public static $protocol = 'http';

    /**
     * @var  string  应用的索引文件
     */
    public static $frontend = 'index.php';

    /**
     * @var  string  引用的url后缀
     */
    public static $url_suffix = '.html';

    /**
     * @var  string  高速缓存目录
     */
    public static $cache_dir;

    /**
     * @var  boolean  是否开启高速缓存
     */
    public static $caching = FALSE;

    /**
     * @var  boolean  是否开启基础测试
     */
    public static $profiling = TRUE;

    /**
     * @var  boolean  是否开启错误管理
     */
    public static $errors = TRUE;

    /**
     * @var  array  在shutdown显示错误类型
     */
    public static $shutdown_errors = array(E_PARSE, E_ERROR, E_USER_ERROR, E_COMPILE_ERROR);

    /**
     * @var  object   日志存储接口对象
     */
    public static $log;

    /**
     * @var  object  配置接口对象
     */
    public static $config;

    /**
     * @var  object  路由接口对象
     */
    public static $router;

    /**
     * @var bool 判断是否初始化系统
     */
    protected static $_init = FALSE;

    /**
     * @var array 加载的错误消息集合
     */
    protected static $_messages = array();

    /**
     * @var array 自动加载的路径
     */
    protected static $_paths = array(APPPATH, SYSPATH);

    /**
     * @var array 已加载缓存文件的目录集合
     */
    protected static $_files = array();

    /**
     * @var bool 高速缓存修改的状态
     */
    protected static $_files_changed = FALSE;

    /**
     * @var string 本地语言(待定)
     */
    public static $_language;

    /**
     * @var array 配置信息集合
     */
    public static $_config;

    /**
     * @var array 路由规则集合
     */
    public static $_routes;

    /**
     * 构建PHP的初始化环境。
     * 增加异常错误处理机制，增加自动加载方法。
     * 通过传入的参数来开启基准测试。
     * 为了安全起见,过滤输入数据，防止不安全数据进入应用
     *
     * @see http://www.php.net/globals
     *
     * @return void
     */
    public function instance(array $settings = NULL)
    {
        if(QuickPHP::$_init)
            return QuickPHP::$_init;

        // 判断配置是否开启基准测试
        if(isset($settings['profiling']))
            QuickPHP::$profiling = (bool) $settings['profiling'];

        // 如果框架开启基准测试，则开始一个测试
        if(QuickPHP::$profiling === TRUE)
            $benchmark = Profiler::start('QuickPHP', __FUNCTION__);

        if(defined('E_DEPRECATED'))
            QuickPHP::$php_errors[E_DEPRECATED] = 'Deprecated';

        // 判读是否显示错误
        if(isset($settings['errors']))
            QuickPHP::$errors = (bool) $settings['errors'];

        // 如果开启了错误提示，自定义异常和错误管理器
        if(QuickPHP::$errors === TRUE)
        {
            set_exception_handler(array('QuickPHP', 'exception_handler'));
            set_error_handler(array('QuickPHP', 'error_handler'));
        }

        // 注册shutdown函数
        register_shutdown_function(array('QuickPHP', 'shutdown_handler'));

        // 如果已经开启了register_globals，销毁 register_globals 所影响的设置
        if(ini_get('register_globals'))
            QuickPHP::globals();

        // 判读是否以命令行方式运行
        QuickPHP::$is_cli = (PHP_SAPI === 'cli');

        // 判读是否是windows操作系统
        QuickPHP::$is_windows = (DIRECTORY_SEPARATOR === '\\');

        // 如果是命令行环境，则开启缓冲
        if(!QuickPHP::$is_cli) ob_start();

        // 判断是否设置系统高速缓存目录，否则为默认值，系统高速缓存用于存储配置器数据以及基准测试数据
        if(isset($settings['cache_dir']))
            QuickPHP::$cache_dir = realpath($settings['cache_dir']);
        else
            QuickPHP::$cache_dir = RUNTIME . '_cache';

        // 判断系统高速缓存目录是否可写
        if(! is_writable(QuickPHP::$cache_dir))
            throw new QuickPHP_Exception('Directory "{0}" must be writable', array(debug::path(QuickPHP::$cache_dir)));

        // 判断系统高速缓存开启是否设置
        if(isset($settings['caching']))
            QuickPHP::$caching = (bool) $settings['caching'];

        // 如果开启则缓存所有导入数据的文件
        if(QuickPHP::$caching === TRUE)
            QuickPHP::$_files = QuickPHP::cache('QuickPHP::find()');

        // 判断是否设置了字符编码
        if(isset($settings['charset']))
            QuickPHP::$charset = strtolower($settings['charset']);

        // 判断是否开启了mbstring扩展，如果开启则设置mbstring内部编码
        if(function_exists('mb_internal_encoding'))
            mb_internal_encoding(QuickPHP::$charset);

        // 判断是否设置域名
        if(isset($settings['domain']))
            QuickPHP::$domain = rtrim($settings['domain'], '/') . '/';

        // 判断是否设置系统入口文件名
        if(isset($settings['frontend']))
            QuickPHP::$frontend = trim($settings['frontend'], '/');

        // 判断魔术引号状态, 5.3以上版本已经关闭魔术引号
        QuickPHP::$magic_quotes = (bool) get_magic_quotes_gpc();

        // 过滤输入数据，防止不安全数据进入应用
        $_GET = QuickPHP::sanitize($_GET);
        $_POST = QuickPHP::sanitize($_POST);
        $_COOKIE = QuickPHP::sanitize($_COOKIE);

        // 关闭基准测试
        if(isset($benchmark))
            Profiler::stop($benchmark);

        QuickPHP::$_init = new QuickPHP();

        return QuickPHP::$_init;
    }

    /**
     * 销毁 register_globals 所影响的设置。
     *
     * if (ini_get('register_globals'))
     * {
     *      QuickPHP::globals();
     * }
     *
     * @return  void
     */
    public static function globals()
    {
        if(isset($_REQUEST['GLOBALS']) or isset($_FILES['GLOBALS']))
            exit("Global variable overload attack detected! Request aborted.\n");

        $global_variables = array_keys($GLOBALS);
        $global_variables = array_diff($global_variables,
            array('_COOKIE', '_ENV', '_GET', '_FILES', '_POST', '_REQUEST', '_SERVER', '_SESSION', 'GLOBALS'));

        foreach ($global_variables as $name)
        {
            unset($GLOBALS[$name]);
        }
    }

    /**
     * 过滤输入数据，防止不安全数据进入应用
     *
     * - 如果启用魔术引号则反引用字符
     * - 标准化所有换行符为LF
     *
     * @param   mixed  变量
     * @return  mixed  序列化后的值
     */
    public static function sanitize($value)
    {
        if(is_array($value) or is_object($value))
        {
            foreach ($value as $key => $val)
            {
                $value[$key] = QuickPHP::sanitize($val);
            }
        }
        elseif(is_string($value))
        {
            if(QuickPHP::$magic_quotes === TRUE)
            {
                $value = stripslashes($value);
            }

            if(strpos($value, "\r") !== FALSE)
            {
                $value = str_replace(array("\r\n", "\r"), "\n", $value);
            }
        }

        return $value;
    }

    /**
     * QuickPHP 框架控制器调度
     *
     * @return  object
     */
    public function dispatch()
    {
        if(QuickPHP::$_init != NULL)
        {
            try
            {
                // 加载控制器文件,并反射这个控制器
                require_once QuickPHP::route()->controller_path;
                $class = new ReflectionClass(ucfirst(QuickPHP::route()->get('controller')) . '_Controller');
            }
            catch(ReflectionException $e)
            {
                throw new QuickPHP_Exception('no_controller', QuickPHP::route()->segments);
            }

            // 判断控制器文件是否是抽象类，以及开启了生产模式，ALLOW_PRODUCTION 常量设置为否
            if($class->isAbstract() or (IN_PRODUCTION and $class->getConstant('ALLOW_PRODUCTION') == FALSE))
            {
                // @todo
                throw new QuickPHP_Exception('controller_is_not_allowed', QuickPHP::route()->segments);
            }

            $controller = $class->newinstance();

            try
            {
                $method = QuickPHP::route()->get('method');

                if($method[0] === '_')
                {
                    throw new QuickPHP_Exception('Method_is_not_exists', array($method));
                }

                $method = $class->getMethod($method);

                if($method->isProtected() or $method->isPrivate())
                {
                    throw new QuickPHP_Exception('protected_or_private_controller_method', array($method));
                }

                $arguments = QuickPHP::route()->get('arguments');
            }
            catch(ReflectionException $e)
            {
                $method = $class->getMethod('__call');
                $arguments = array(QuickPHP::route()->get('method'), QuickPHP::route()->get('arguments'));
            }

            $class->getMethod('before')->invoke($controller);
            $method->invokeArgs($controller, $arguments);
            $class->getMethod('after')->invoke($controller);
        }
    }

    /**
     * 自定义自动加载器
     * QuickPHP_为前缀的类名为框架自带类，就是存放在framework目录中的文件.
     * 可以直接省略前缀使用例如 QuickPHP_url::base(),可以直接简化为url::base().
     * 没有QuickPHP开头的类名则为自定义类名.
     * 同名类的加载顺序为，APPPATH，MODPATH，SYSPATH。依次从前向后。
     * 说明APPPATH和MODPATH的url类，和QuickPHP_url视为同名
     *
     * @param   string  类名
     * @return  bool
     */
    public static function autoloader($class)
    {
        if(class_exists($class, FALSE) or class_exists($class))
            return TRUE;

        $segments   = explode("_", $class);
        $system     = FALSE;

        if(($segments[0]) == 'QuickPHP')
        {
            array_shift($segments);
            $system = TRUE;
        }

        $suffix = count($segments) > 1 ? end($segments) : NULL;

        if($suffix === 'Controller')
        {
            array_pop($segments);
            $directory  = 'controllers';
            $filepath = $directory .'/'. strtolower(implode("/", $segments)) . EXT;
        }
        elseif($suffix === 'Model')
        {
            array_pop($segments);
            $directory  = 'models';
            $filepath = $directory .'/'. strtolower(implode("/", $segments)) . EXT;
        }
        else
        {
            $directory  = ($segments[0] < 'a') ? 'libraries' : 'helpers';
            $filepath   = $directory .'/'. (implode("/", $segments)) . EXT;
        }

        if($system === FALSE)
        {
            if(file_exists(APPPATH . $filepath))
            {
                require_once APPPATH . $filepath;
            }
            elseif(file_exists(SYSPATH . $filepath))
            {
                require_once SYSPATH . $filepath;

                if($system === FALSE)
                {
                    if(strtolower($suffix) === 'abstract')
                    {
                        $alis = "abstract class $class extends QuickPHP_$class{}";
                    }
                    elseif(strtolower($suffix) === 'interface')
                    {
                        $alis = "interface $class extends QuickPHP_$class{}";
                    }
                    else
                    {
                        $alis = "class $class extends QuickPHP_$class{}";
                    }

                    eval($alis);
                }
            }
        }
        else
        {
            if(file_exists(SYSPATH . $filepath))
            {
                require_once SYSPATH . $filepath;
            }

        }

        return true;
    }

    /**
     * 返回当前已经包含的文件路径，包括 APPPATH, SYSPATH.
     *
     * @return  array
     */
    public static function get_include_paths()
    {
        return QuickPHP::$_paths;
    }

    /**
     * 寻找文件路径
     *
     * // 返回绝对路径 views/template.php
     * QuickPHP::find('views', 'template');
     *
     * // 返回绝对路径 media/css/style.css
     * QuickPHP::find('media', 'css/style', 'css');
     *
     * // Returns an array of all the "mimes" configuration file
     * QuickPHP::find('config', 'mimes');
     *
     * @param   string   目录名
     * @param   string   含有子目录和文件名
     * @param   string   扩展名
     * @param   boolean  是否返回数组形式的文件列表?
     * @return  array    如果$array为真返回数组形式的文件列表
     * @return  string   单文件路径
     */
    public static function find($dir, $file, $ext = NULL, $array = FALSE)
    {
        $ext  = ($ext === NULL) ? EXT : '.' . $ext;
        $path = $dir . '/' . $file . $ext;

        if(QuickPHP::$caching === TRUE and isset(QuickPHP::$_files[$path]))
            return QuickPHP::$_files[$path];

        if(QuickPHP::$profiling === TRUE and class_exists('Profiler', FALSE))
            $benchmark = Profiler::start('QuickPHP', __FUNCTION__ );

        if($array or $dir === 'config' or $dir === 'messages' or $dir === 'vendors')
        {
            $paths = array_reverse(QuickPHP::$_paths);
            $found = array();

            foreach ($paths as $dir)
            {
                if(is_file($dir . $path))
                    $found[] = $dir . $path;
            }
        }
        else
        {
            $found = FALSE;

            foreach (QuickPHP::$_paths as $dir)
            {
                if(is_file($dir . $path))
                {
                    $found = $dir . $path;
                    break;
                }
            }
        }

        if(QuickPHP::$caching === TRUE)
        {
            QuickPHP::$_files[$path]  = $found;
            QuickPHP::$_files_changed = TRUE;
        }

        if(isset($benchmark))
            Profiler::stop($benchmark);

        return $found;
    }

    /**
     * 加载一个文件并返回输出
     *
     * $foo = QuickPHP::load('foo.php');
     *
     * @param   string
     * @return  mixed
     */
    public static function load($file)
    {
        return include $file;
    }

    /**
     * 创建一组新的日志对象
     *
     * @param   string   组名
     * @return  QuickPHP_Log
     */
    public static function log($group)
    {
        if(!isset(QuickPHP::$log))
            QuickPHP::$log = Log::instance()->attach(new Log_Driver_File());

        return QuickPHP::$_log;
    }

    /**
     * 创建一组新的配置对象
     *
     * @param   string   组名
     * @return  QuickPHP_Config
     */
    public static function config($group)
    {
        if(!isset(QuickPHP::$config))
            QuickPHP::$config = Config::instance()->attach(new Config_Driver_File());

        if(strpos($group, '.') !== FALSE)
            list($group, $path) = explode('.', $group, 2);

        if(! isset(QuickPHP::$_config[$group]))
            QuickPHP::$_config[$group] = QuickPHP::$config->load($group);

        if(isset($path))
            return arr::path(QuickPHP::$_config[$group], $path);
        else
            return QuickPHP::$_config[$group];
    }

    /**
     * 返回路由处理器
     *
     * @return  QuickPHP_Router
     */
    public static function route()
    {
        if(empty(QuickPHP::$router))
            QuickPHP::$router = Router::instance();

        return QuickPHP::$router;
    }

    /**
     * 内核级的高速缓存，用以存储配置器，基准测试等数据
     *
     * // 存储高速缓存
     * QuickPHP::cache('foo', 'hello, world');
     *
     * // 读取高速缓存
     * $foo = QuickPHP::cache('foo');
     *
     * [ref-var]: http://php.net/var_export
     *
     * @throws  QuickPHP_Exception
     * @param   string   缓存名称
     * @param   mixed    缓存数据
     * @param   integer  有效期限，单位秒
     * @return  mixed    返回字符串、数组或者空
     */
    public static function cache($name, $data = NULL, $lifetime = 60)
    {
        $file = sha1($name) . '.txt';
        $dir  = QuickPHP::$cache_dir . DIRECTORY_SEPARATOR . $file[0] . $file[1] . DIRECTORY_SEPARATOR;

        try
        {
            if($data === NULL)
            {
                if(is_file($dir . $file))
                {
                    if((time() - filemtime($dir . $file)) < $lifetime)
                    {
                        return QuickPHP::find(QuickPHP::$cache_dir, $file);
                    }
                    else
                    {
                        try
                        {
                            unlink($dir . $file);
                        }
                        catch(Exception $e)
                        {
                            return NULL;
                        }
                    }
                }

                return NULL;
            }

            if(! is_dir($dir))
            {
                mkdir($dir, 0777, TRUE);
                chmod($dir, 0777);
            }

            return (bool) file_put_contents($dir . $file, var_export($data, true));
        }
        catch(Exception $e)
        {
            throw $e;
        }
    }

    /**
     * 从消息文件中取得一条消息，消息文件均存储在messages目录
     *
     * // 从 messages/text.php 获取 "username" 的值
     * $username = QuickPHP::message('text', 'username');
     *
     * @param   string  要获取消息的文件名
     * @param   string  要获得的消息键的路径
     * @param   mixed   默认值，如果路径不存在则返回该值
     * @return  string  指定路径的消息字符串
     * @return  array   当没有指定路径,则返回完整数组的消息列表
     * @uses    arr::merge
     * @uses    arr::path
     */
    public static function message($file, $path = NULL, $default = NULL)
    {
        if(! isset(QuickPHP::$_messages[$file]))
        {
            QuickPHP::$_messages[$file] = array();

            $files = QuickPHP::find('messages', $file);

            if($files)
            {
                foreach ($files as $msg)
                {
                    QuickPHP::$_messages[$file] = array_merge(QuickPHP::$_messages[$file], QuickPHP::load($msg));
                }
            }
        }

        if($path === NULL)
        {
            return QuickPHP::$_messages[$file];
        }
        else
        {
            return arr::path(QuickPHP::$_messages[$file], $path, $default);
        }
    }

    /**
     * 系统自定义的错误管理器, 将所有 PHP 错误转向 ErrorExceptions.
     *
     * @throws  ErrorException
     * @return  TRUE
     */
    public static function error_handler($code, $error, $file = NULL, $line = NULL)
    {
        if(error_reporting() & $code)
            throw new ErrorException($error, $code, 0, $file, $line);

        return TRUE;
    }

    /**
     * 系统自定义异常处理器，显示错误信息，异常的来源，和堆栈跟踪的错误等。
     *
     * @uses    QuickPHP::exception_text
     * @param   object   exception object
     * @return  boolean
     */
    public static function exception_handler(Exception $e)
    {
        try
        {
            $type = get_class($e);
            $code = $e->getCode();
            $file = $e->getFile();
            $line = $e->getLine();
            $error = QuickPHP::exception_text($e);
            $message = $e->getMessage();

            if(is_object(QuickPHP::$log))
            {
                QuickPHP::$log->add(QuickPHP::ERROR, $error);
                QuickPHP::$log->write();
            }

            if(QuickPHP::$is_cli === TRUE)
            {
                return debug_print_backtrace();
            }

            $trace = $e->getTrace();
            array_shift($trace);

            if($e instanceof ErrorException)
            {
                if(isset(QuickPHP::$php_errors[$code]))
                {
                    $code = QuickPHP::$php_errors[$code];
                }

                if(version_compare(PHP_VERSION, '5.3', '<'))
                {
                    for ($i = count($trace) - 1; $i > 0; --$i)
                    {
                        if(isset($trace[$i - 1]['args']))
                        {
                            $trace[$i]['args'] = $trace[$i - 1]['args'];
                            unset($trace[$i - 1]['args']);
                        }
                    }
                }
            }

            if(! headers_sent())
            {
                header('Content-Type: text/html; charset=' . QuickPHP::$charset, TRUE, 500);
            }

            ob_start();
            include QuickPHP::find('views', 'error');
            echo ob_get_clean();
            return TRUE;
        }
        catch(Exception $e)
        {
            ob_get_level() and ob_clean();
            echo QuickPHP::exception_text($e), PHP_EOL;
            exit(1);
        }
    }

    /**
     * register_shutdown_function 所有程序在执行结束后调用该方法
     *
     * @uses    QuickPHP::exception_handler
     * @return  void
     */
    public static function shutdown_handler()
    {
        if(! QuickPHP::$_init)
            return;

        try
        {
            if(QuickPHP::$caching === TRUE and QuickPHP::$_files_changed === TRUE)
            {
                QuickPHP::cache('QuickPHP::find()', QuickPHP::$_files);
            }
        }
        catch(Exception $e)
        {
            QuickPHP::exception_handler($e);
        }

        if(QuickPHP::$is_cli)
        {
            error_get_last();
            exit(1);
        }

        if(QuickPHP::$errors and $error = error_get_last() and in_array($error['type'], QuickPHP::$shutdown_errors))
        {
            ob_get_level() and ob_clean();
            $ErrorException = new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']);
            QuickPHP::exception_handler($ErrorException);
            exit(1);
        }

        if(QuickPHP::$profiling === TRUE)
            include QuickPHP::find('views', 'stats');
    }

    /**
     * 获取以文本方式异常错误信息:
     *
     * 返回以下格式:
     * Error [ Code ]: Message ~ File [ Line ]
     *
     * @param   object  Exception
     * @return  string
     */
    public static function exception_text(Exception $e)
    {
        $trace  = sprintf('%s [ %s ]: %s ~ %s [ %d ]', get_class($e),
            $e->getCode(), strip_tags($e->getMessage()), debug::path($e->getFile()),
            $e->getLine()).PHP_EOL;

        foreach ($e->getTrace() as $i => $step)
        {
            $trace .= "  " . sprintf('%s :%s ~ %s [ %d ]', $step['file'], null, $step['class'] . $step['type'] . $step['function'], $step['line']).PHP_EOL;
        }

        return $trace;
    }

}